package org.smoozik.services.sequence;

import java.io.IOException;
import java.util.HashMap;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.smoozik.app.ihm.Constants;
import org.smoozik.services.musictheory.Chord;
import org.smoozik.services.musictheory.Interval;
import org.smoozik.services.musictheory.Note;
import org.smoozik.services.musictheory.Partition;
import org.smoozik.services.musictheory.Token;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class SongReader implements ContentHandler {

	private Sequence currentSequence;
	private Partition currentPartition;
	private Song song;
	private Chord currentChord;
	private HashMap<String, Partition> partitions;

	// Elements
	public static final String ELT_SONG = "song";
	public static final String ELT_SEQUENCE = "sequence";
	public static final String ELT_NOTE = "note";
	public static final String ELT_CHORD = "chord";
	public static final String ELT_INTERVAL = "interval";
	public static final String ELT_PARTITION = "partition";

	// default values
	public static final int DEFAULT_SPEED = 120;
	public static final int DEFAULT_NBTIMES = 4;

	// Attributes
	public static final String ATTR_SPEED = "speed";
	public static final String NBTIMES_SPEED = "nbTimes";
	public static final String ATTR_INDEX = "index";
	public static final String ATTR_HEIGHT = "height";
	public static final String ATTR_PLACE = "place";
	public static final String ATTR_DURATION = "duration";
	public static final String ATTR_VELOCITY = "velocity";
	public static final String ATTR_DEGRE = "degre";
	public static final String ATTR_NBHALFTONS = "nbHalfTons";
	public static final String ATTR_NAME = "name";
	public static final String ATTR_KEY = "key";

	public SongReader() {
		super();
		partitions = new HashMap<String, Partition>();
	}

	public Song getSong() {

		for (Sequence seq : song.getSequences()) {
			if (seq != null) {
				seq.sort();
			}
		}
		return song;
	}

	@Override
	public void setDocumentLocator(Locator locator) {

	}

	/**
	 * Evenement envoye au demarrage du parse du flux xml.
	 * 
	 * @throws SAXException
	 *             en cas de probleme quelquonque ne permettant pas de se lancer
	 *             dans l'analyse du document.
	 * @see org.xml.sax.ContentHandler#startDocument()
	 */
	public void startDocument() throws SAXException {
	}

	/**
	 * Evenement envoye a la fin de l'analyse du flux xml.
	 * 
	 * @throws SAXException
	 *             en cas de probleme quelquonque ne permettant pas de
	 *             considerer l'analyse du document comme etant complete.
	 * @see org.xml.sax.ContentHandler#endDocument()
	 */
	public void endDocument() throws SAXException {
	}

	/**
	 * Debut de traitement dans un espace de nommage.
	 * 
	 * @param prefixe
	 *            utilise pour cet espace de nommage dans cette partie de
	 *            l'arborescence.
	 * @param URI
	 *            de l'espace de nommage.
	 * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String,
	 *      java.lang.String)
	 */
	public void startPrefixMapping(String prefix, String URI)
			throws SAXException {
	}

	/**
	 * Fin de traitement de l'espace de nommage.
	 * 
	 * @param prefixe
	 *            le prefixe choisi a l'ouverture du traitement de l'espace
	 *            nommage.
	 * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
	 */
	public void endPrefixMapping(String prefix) throws SAXException {
	}

	/**
	 * Evenement recu a chaque fois que l'analyseur rencontre une balise xml
	 * ouvrante.
	 * 
	 * @param nameSpaceURI
	 *            l'url de l'espace de nommage.
	 * @param localName
	 *            le nom local de la balise.
	 * @param rawName
	 *            nom de la balise en version 1.0
	 *            <code>nameSpaceURI + ":" + localName</code>
	 * @throws SAXException
	 *             si la balise ne correspond pas a ce qui est attendu, comme
	 *             par exemple non respect d'une dtd.
	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
	 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String nameSpaceURI, String localName,
			String rawName, Attributes attributs) throws SAXException {

		if (ELT_PARTITION.equals(localName) || ELT_PARTITION.equals(rawName)) {
			String keyVal = attributs.getValue(ATTR_KEY);
			int key = Constants.G_KEY;
			if (keyVal != null) {
				key = Integer.parseInt(keyVal);
			}
			String nameVal = attributs.getValue(ATTR_NAME);
			if (keyVal == null) {
				nameVal = "p" + partitions.size();
			}
			Partition partition = new Partition(key);
			partitions.put(nameVal, partition);

		} else if (ELT_SEQUENCE.equals(localName)
				|| ELT_SEQUENCE.equals(rawName)) {
			currentSequence = new Sequence();
			String keyVal = attributs.getValue(ATTR_KEY);
			int key = Constants.G_KEY;
			if (keyVal != null) {
				key = Integer.parseInt(keyVal);
			}
			currentPartition = new Partition(key);

			if (song == null) {
				song = new Song(DEFAULT_SPEED);
			}

			song.getSequences().add(currentSequence);

		} else if (ELT_SONG.equals(localName) || ELT_SONG.equals(rawName)) {

			int nbTimes = DEFAULT_NBTIMES;
			String nbTimesVal = attributs.getValue(NBTIMES_SPEED);
			if (nbTimesVal != null) {
				nbTimes = Integer.parseInt(nbTimesVal);
			}
			int speed = DEFAULT_SPEED;
			String speedVal = attributs.getValue(ATTR_SPEED);
			if (speedVal != null) {
				speed = Integer.parseInt(speedVal);
			}

			song = new Song(speed);
			song.setNbTimesByMeasure(nbTimes);

		} else if (ELT_NOTE.equals(localName) || ELT_NOTE.equals(rawName)) {
			int index = Integer.parseInt(attributs.getValue(ATTR_INDEX));
			String heightVal = attributs.getValue(ATTR_HEIGHT);
			String velocityVal = attributs.getValue(ATTR_VELOCITY);
			String keyVal = attributs.getValue(ATTR_KEY);
			String keyPlace = attributs.getValue(ATTR_PLACE);
			String keyDuration = attributs.getValue(ATTR_DURATION);
			int key = Constants.G_KEY;
			Partition partition = null;
			int height = 0;
			int velocity = 0;
			Note note = null;
			double place = Double.parseDouble(keyPlace);
			double duration = Double.parseDouble(keyDuration);

			if (keyVal != null) {
				partition = partitions.get(keyVal);

				if (partition == null) {
					key = Integer.parseInt(keyVal);
					partition = new Partition(key);
					partitions.put(keyVal, partition);
				}
			} else {
				partition = currentPartition;
			}
			note = new Note(partition, index);

			if (heightVal != null) {
				height = Integer.parseInt(heightVal);
				note.setHauteur(height);
			}
			Token t = new Token(note);
			if (velocityVal != null) {
				velocity = Integer.parseInt(velocityVal);
				t.setVelocity(velocity);
			}
			t.setDuration(duration);
			t.setPlace(place);

			currentSequence.add(t);
		} else if (ELT_CHORD.equals(localName) || ELT_CHORD.equals(rawName)) {
			int key = Constants.G_KEY;
			int index = Integer.parseInt(attributs.getValue(ATTR_INDEX));
			String keyVal = attributs.getValue(ATTR_KEY);
			String heightVal = attributs.getValue(ATTR_HEIGHT);
			String name = attributs.getValue(ATTR_NAME);
			String velocityVal = attributs.getValue(ATTR_VELOCITY);
			int height = 0;
			int velocity = 0;
			Note note = null;
			Partition partition = null;

			if (keyVal != null) {
				partition = partitions.get(keyVal);

				if (partition == null) {
					key = Integer.parseInt(keyVal);
					partition = new Partition(key);
					partitions.put(keyVal, partition);
				}
			} else {
				partition = currentPartition;
			}
			note = new Note(partition, index);

			if (heightVal != null) {
				height = Integer.parseInt(heightVal);
				note.setHauteur(height);
			}
			currentChord = new Chord(note);

			if (name != null) {
				currentChord.setIntervals(getIntervalsForName(name));
			}
			Token t = new Token(currentChord);
			if (velocityVal != null) {
				velocity = Integer.parseInt(velocityVal);
				t.setVelocity(velocity);
			}
			double place = Double.parseDouble(attributs.getValue(ATTR_PLACE));
			double duration = Double.parseDouble(attributs
					.getValue(ATTR_DURATION));
			t.setDuration(duration);
			t.setPlace(place);

			currentSequence.add(t);
		} else if (ELT_INTERVAL.equals(localName)
				|| ELT_INTERVAL.equals(rawName)) {
			if (currentChord != null) {
				int degre = Integer.parseInt(attributs.getValue(ATTR_DEGRE));
				int nbHalfTons = Integer.parseInt(attributs
						.getValue(ATTR_NBHALFTONS));
				Interval interval = new Interval(degre, nbHalfTons);

				currentChord.addInterval(interval);
			}
		}
	}

	public Interval[] getIntervalsForName(String name) {
		Interval[] tmpChords = null;

		for (int i = 0; i < Constants.CHORD_NAMES.length; i++) {
			if (Constants.CHORD_NAMES[i].equals(name)) {
				tmpChords = Constants.CHORDS[i];
			}
		}

		return tmpChords;
	}

	/**
	 * Evenement recu a chaque fermeture de balise.
	 * 
	 * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
	 *      java.lang.String, java.lang.String)
	 */
	public void endElement(String nameSpaceURI, String localName, String rawName)
			throws SAXException {

	}

	/**
	 * Evenement recu a chaque fois que l'analyseur rencontre des caracteres
	 * (entre deux balises).
	 * 
	 * @param ch
	 *            les caracteres proprement dits.
	 * @param start
	 *            le rang du premier caractere a traiter effectivement.
	 * @param end
	 *            le rang du dernier caractere a traiter effectivement
	 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
	 */
	public void characters(char[] ch, int start, int end) throws SAXException {
	}

	/**
	 * Recu chaque fois que des caracteres d'espacement peuvent etre ignores au
	 * sens de XML. C'est a dire que cet evenement est envoye pour plusieurs
	 * espaces se succedant, les tabulations, et les retours chariot se
	 * succedants ainsi que toute combinaison de ces trois types d'occurrence.
	 * 
	 * @param ch
	 *            les caracteres proprement dits.
	 * @param start
	 *            le rang du premier caractere a traiter effectivement.
	 * @param end
	 *            le rang du dernier caractere a traiter effectivement
	 * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
	 */
	public void ignorableWhitespace(char[] ch, int start, int end)
			throws SAXException {
	}

	/**
	 * Rencontre une instruction de fonctionnement.
	 * 
	 * @param target
	 *            la cible de l'instruction de fonctionnement.
	 * @param data
	 *            les valeurs associees a cette cible. En general, elle se
	 *            presente sous la forme d'une serie de paires nom/valeur.
	 * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String,
	 *      java.lang.String)
	 */
	public void processingInstruction(String target, String data)
			throws SAXException {
	}

	/**
	 * Recu a chaque fois qu'une balise est evitee dans le traitement a cause
	 * d'un probleme non bloque par le parser. Pour ma part je ne pense pas que
	 * vous en ayez besoin dans vos traitements.
	 * 
	 * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
	 */
	public void skippedEntity(String arg0) throws SAXException {
	}

	public static void main(String[] args) throws SAXException, IOException,
			ParserConfigurationException {
		SongReader sr = new SongReader();

		SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
		XMLReader saxReader = saxParser.getXMLReader();
		saxReader.setContentHandler(sr);
		saxReader
				.parse(".\\src\\org\\smoozik\\app\\common\\partition\\resources\\sequenceTest.xml");

		// Sequence seq = sr.getSequence();
	}
}
